package com.ssm.basis.web.servlet.filter;

import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.web.filter.AbstractRequestLoggingFilter;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;

/**
 * https://blog.csdn.net/f641385712/article/details/87793736
 * 默认只打印 POST、PUT、DELETE 方法的请求日志
 * 会计算出请求耗时、client 请求的 ip 地址等等，contentType 也会记录打印出来，方便查找问题
 */
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class RequestLogFilter extends AbstractRequestLoggingFilter {
    // 这些配置都可以在 init-param 中进行设置。基于注解，这里就不要这么麻烦，统一在初始化的时候设置值
    // private boolean includeQueryString = false;
    // private boolean includeClientInfo = false;
    // private boolean includeHeaders = false;
    // private boolean includePayload = false;
    private static final String PROCESS_START_TIME_SUFFIX = ".PROCESS_START_TIME";

    @Override
    protected void initFilterBean() throws ServletException {
        super.setIncludeQueryString(true);
        // super.setIncludeClientInfo(true); // Nginx 代理后就无法获得正确 IP 了
        // 因为 headers 里的信息太多，这里只手动把 content-type 等关键信息输出
        super.setIncludeHeaders(false);
        super.setIncludePayload(true);
        super.setMaxPayloadLength(Integer.MAX_VALUE / 1024 / 8);

        // 头信息
        // super.setBeforeMessagePrefix("请求开始 [");
        // super.setBeforeMessageSuffix("]");
        // super.setAfterMessagePrefix("请求结束 [");
        // super.setAfterMessageSuffix("]");

        logger.debug("RequestLogFilter initFilterBean()...");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        FilterConfig filterConfig = super.getFilterConfig();
        ServletContext servletContext = super.getServletContext();
        Environment environment = super.getEnvironment();
        // filterChain.doFilter(request, response);
        super.doFilterInternal(request, response, filterChain);
    }

    @Override
    protected boolean shouldLog(HttpServletRequest request) {
        String method = request.getMethod();
        return logger.isDebugEnabled() &&
                (HttpMethod.POST.matches(method) || HttpMethod.PUT.matches(method) || HttpMethod.DELETE.matches(method));
    }

    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        logger.debug(calcRequestTime(request)
                .concat(getConfigTypeLog(request))
                .concat(getThreadId())
                .concat(getRemoteIP(request))
                .concat(message));
    }

    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        logger.debug(calcRequestTime(request)
                .concat(getConfigTypeLog(request))
                .concat(getThreadId())
                .concat(message));
    }

    private String getConfigTypeLog(HttpServletRequest request) { // 拼装 contentType
        String contentType = request.getContentType();
        String method = request.getMethod();
        return "[contentType=" + contentType + "] " + method.toUpperCase() + " ";
    }

    private String calcRequestTime(HttpServletRequest request) { // 计算请求耗时
        long mills = 0;
        String requestTimeUniqueName = getRequestTimeUniqueName();

        Object processStartTime = request.getAttribute(requestTimeUniqueName);
        if (processStartTime == null) { // 首次放置值
            request.setAttribute(requestTimeUniqueName, Instant.now());
        } else { // 请求结束的处理，开始计算
            Instant start = (Instant) processStartTime;
            Instant now = Instant.now();
            mills = Duration.between(start, now).toMillis();

            request.removeAttribute(requestTimeUniqueName);
        }
        return mills == 0 ? "" : ("[耗时:" + mills + "ms] ");
    }

    private String getRequestTimeUniqueName() {
        return this.getClass().getName().concat(PROCESS_START_TIME_SUFFIX);
    }

    private String getThreadId() {
        return "[ThreadId:" + Thread.currentThread().getId() + "] ";
    }

    private String getRemoteIP(HttpServletRequest request) {
        String clientIP = request.getHeader("x-forwarded-for");
        if (clientIP == null) {
            return request.getRemoteAddr();
        }
        return "[client:" + clientIP + "] ";
    }
}
